/*
 * Copyright (c) 2011, Marc-Andre Ferland - madrang@gmail.com
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the The Warrent Team nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *     * This software is free for non-commercial use. You may not use this
 *       software, in whole or in part, in support of any commercial product
 *       without the express consent of the author.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE WARRENT TEAM BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace OsHelper.UsbNet.LibUsb.Descriptors
{
	public class ConfigDescriptor
	{
		private ConfigDescriptor ()
		{
		}
		
		[StructLayout(LayoutKind.Sequential)]
		internal class ConfigInfo
		{
			public readonly byte Length;
			public readonly UsbNet.Descriptors.DescriptorType DescriptorType;
			public readonly short TotalLength;
			public readonly byte NumberInterfaces;
			public readonly byte ConfigurationValue;
			public readonly byte ConfigurationStringIndex;
			public readonly byte Attributes;
			public readonly byte MaxPower;

			/// <summary>
			/// Array of interfaces supported by this configuration.
			/// The length of this array is determined by the bNumInterfaces field.
			/// </summary>
			public readonly IntPtr InterfacesHandle;

			/// <summary>
			/// Extra descriptors.
			/// If libusb encounters unknown configuration descriptors, it will store them here, should you wish to parse them.
			/// </summary>
			public readonly IntPtr ExtraBytesHandle;

			/// <summary>
			/// Length of the extra descriptors, in bytes.
			/// </summary>
			public readonly int ExtraLength;
		}

		private class ConfigHandle : Handle
		{
			private ConfigHandle () : base()
			{
			}

			protected override bool ReleaseHandle ()
			{
				if (!this.IsInvalid) {
					ConfigHandle.FreeConfigDescriptor (this.handle);
					this.SetHandleAsInvalid ();
				}
				return true;
			}

			[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_free_config_descriptor")]
			private static extern void FreeConfigDescriptor (IntPtr ConfigHandle);
		}

		/// <summary>
		/// Get the USB configuration descriptor for the currently active configuration.
		/// </summary>
		/// <param name="MyProfile">
		/// A <see cref="LibUsb.Profile.Profile"/>
		/// </param>
		public static ConfigDescriptor FromProfile (LibUsb.Profile MyProfile)
		{
			ConfigHandle MyHandle;
			
			LibUsb.Error ErrorCode = ConfigDescriptor.GetActiveConfigDescriptor (MyProfile, out MyHandle);
			
			switch (ErrorCode) {
			case LibUsb.Error.Success:
				return new ConfigDescriptor (MyHandle);
			case LibUsb.Error.NoMem:
				throw new OutOfMemoryException ();
			case LibUsb.Error.AccessDenied:
				throw new UnauthorizedAccessException ();
			case LibUsb.Error.NoDevice:
				throw new DeviceNotFoundException ();
			default:
				throw new Exception ("Unknown error in ConfigHandle:GetActiveConfigDescriptor, LibUsb Code : " + ErrorCode);
			}
		}

		/// <summary>
		/// Get the USB configuration descriptor for the currently active configuration.
		/// </summary>
		/// <param name="MyProfile">
		/// A <see cref="LibUsb.Profile.Profile"/>
		/// </param>
		public static ConfigDescriptor FromIndex (LibUsb.Profile MyProfile, byte Index)
		{
			ConfigHandle MyHandle;
			
			LibUsb.Error ErrorCode = ConfigDescriptor.GetConfigDescriptorByIndex (MyProfile, Index, out MyHandle);
			
			switch (ErrorCode) {
			case LibUsb.Error.Success:
				return new ConfigDescriptor (MyHandle);
			case LibUsb.Error.NoMem:
				throw new OutOfMemoryException ();
			case LibUsb.Error.AccessDenied:
				throw new UnauthorizedAccessException ();
			case LibUsb.Error.NoDevice:
				throw new DeviceNotFoundException ();
			default:
				throw new Exception ("Unknown error in ConfigHandle:GetConfigDescriptor, LibUsb Code : " + ErrorCode);
			}
		}

		/// <summary>
		/// Get the USB configuration descriptor for the currently active configuration.
		/// </summary>
		/// <param name="MyProfile">
		/// A <see cref="LibUsb.Profile.Profile"/>
		/// </param>
		public static ConfigDescriptor FromValue (LibUsb.Profile MyProfile, byte ConfigurationValue)
		{
			ConfigHandle MyHandle;
			
			LibUsb.Error ErrorCode = ConfigDescriptor.GetConfigDescriptorByValue (MyProfile, ConfigurationValue, out MyHandle);
			
			switch (ErrorCode) {
			case LibUsb.Error.Success:
				return new ConfigDescriptor (MyHandle);
			case LibUsb.Error.NoMem:
				throw new OutOfMemoryException ();
			case LibUsb.Error.AccessDenied:
				throw new UnauthorizedAccessException ();
			case LibUsb.Error.NoDevice:
				throw new DeviceNotFoundException ();
			default:
				throw new Exception ("Unknown error in ConfigHandle:GetActiveConfigDescriptor, LibUsb Code : " + ErrorCode);
			}
		}

		/// <summary>
		/// A collection of alternate settings for a particular USB interface.
		/// </summary>
		[StructLayout(LayoutKind.Sequential)]
		private class Interface
		{
			/// <summary>
			/// Array of interface descriptors.
			/// The length of this array is determined by the NumberAltSetting field.
			/// </summary>
			public readonly IntPtr AltSettingHandle;

			/// <summary>
			/// The number of alternate settings that belong to this interface.
			/// </summary>
			public readonly int NumberAltSetting;

			/// <summary>
			/// Array of interface descriptors.
			/// The length of this array is determined by the NumberAltSetting field.
			/// </summary>
			public IEnumerable<InterfaceDescriptor> InterfaceList ()
			{
				for (int i = 0; i < this.NumberAltSetting; i++) {
					IntPtr NextInterface = new IntPtr (this.AltSettingHandle.ToInt64 () + (Marshal.SizeOf (typeof(InterfaceDescriptor.InterfaceInfo)) * i));
					yield return new InterfaceDescriptor (NextInterface);
				}
			}
		}

		public override IEnumerable<UsbNet.Descriptors.InterfaceDescriptor> InterfaceList ()
		{
			ConfigInfo Inf = (ConfigInfo)Marshal.PtrToStructure (this.DangerousGetHandle (), typeof(ConfigInfo));
			
			for (int i = 0; i < this.NumberInterfaces; i++) {
				
				IntPtr NextInterface = new IntPtr (Inf.InterfacesHandle.ToInt64 () + (Marshal.SizeOf (typeof(ConfigDescriptor.Interface)) * i));
				Interface UsbInterface = (ConfigDescriptor.Interface)Marshal.PtrToStructure (NextInterface, typeof(ConfigDescriptor.Interface));
				
				foreach (InterfaceDescriptor item in UsbInterface.InterfaceList ()) {
					yield return item;
				}
			}
		}

		public override byte[] ExtraBytes {
			get {
				ConfigInfo Inf = (ConfigInfo)Marshal.PtrToStructure (this.DangerousGetHandle (), typeof(ConfigInfo));
				
				if (Inf.ExtraLength <= 0)
					return new byte[0];
				
				byte[] bytes = new byte[Inf.ExtraLength];
				Marshal.Copy (Inf.ExtraBytesHandle, bytes, 0, bytes.Length);
				return bytes;
			}
		}

		/// <summary>
		/// Get the USB configuration descriptor for the currently active configuration.
		/// </summary>
		/// <returns>
		/// <list type="bullet">
		/// <item>0 on success</item>
		/// <item><see cref="Error.ErrorNotFound"/> if the device is in unconfigured state </item>
		/// <item>another <see cref="Error"/> code on error</item>
		/// </list>
		/// </returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_get_active_config_descriptor")]
		private static extern UsbNet.LibUsb.Error GetActiveConfigDescriptor ([In] UsbNet.LibUsb.Profile ProfileHandle, [Out] out ConfigHandle ConfigHandle);

		/// <summary>
		/// Get a USB configuration descriptor based on its index. 
		/// </summary>
		/// <remarks>
		/// <note type="tip">This is a non-blocking function which does not involve any requests being sent to the device.</note>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="desc"/></note>
		/// </remarks>
		/// <param name="deviceProfileHandle">A device profile handle.</param>
		/// <param name="configIndex">The index of the configuration you wish to retrieve.</param>
		/// <param name="configHandle">A config handle.</param>
		/// <returns>
		/// <list type="bullet">
		/// <item>0 on success</item>
		/// <item><see cref="Error.ErrorNotFound"/> if the device is in unconfigured state </item>
		/// <item>another <see cref="Error"/> code on error</item>
		/// </list>
		/// </returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_get_config_descriptor")]
		private static extern UsbNet.LibUsb.Error GetConfigDescriptorByIndex ([In] LibUsb.Profile ProfileHandle, byte Index, [Out] out ConfigHandle ConfigHandle);

		/// <summary>
		/// Get a USB configuration descriptor with a specific bConfigurationValue.
		/// </summary>
		/// <remarks>
		/// <note type="tip">This is a non-blocking function which does not involve any requests being sent to the device.</note>
		/// <note type="tip" title="Libusb-1.0 API:"><seelibusb10 group="desc"/></note>
		/// </remarks>
		/// <param name="deviceProfileHandle">A device profile handle.</param>
		/// <param name="bConfigurationValue">The bConfigurationValue of the configuration you wish to retrieve.</param>
		/// <param name="configHandle">A config handle.</param>
		/// <returns>
		/// <list type="bullet">
		/// <item>0 on success</item>
		/// <item><see cref="Error.ErrorNotFound"/> if the device is in unconfigured state </item>
		/// <item>another <see cref="Error"/> code on error</item>
		/// </list>
		/// </returns>
		[DllImport("libusb-1.0.dll", SetLastError = false, EntryPoint = "libusb_get_config_descriptor_by_value")]
		private static extern UsbNet.LibUsb.Error GetConfigDescriptorByValue ([In] LibUsb.Profile ProfileHandle, byte ConfigurationValue, [Out] out ConfigHandle ConfigHandle);
	}
}
